
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

using Swaf;
using Swaf.BizObj;
using Swaf.Config;
using Swaf.Container;
using Swaf.Resource.Localization;

namespace Swaf.Resource
{
	/// <summary>
	/// Provides a message store that can return a text string based on a message id
	/// and can replace embedded macros with parameters passed to it.</summary>
	/// <author>Matthew Denman</author>
	public class ResourceManager : MarshalByRefObject, IResourceManager, IConfigPartner
	{
		protected static string[] s_shortNumericFormats = new string[] { "C", "D", "E", "F", "G", "N", "P", "R", "X" };
		protected static string[] s_shortDateTimeFormats = new string[] { "d", "D", "t", "T", "f", "F", "g", "G", "M", "R", "s", "U", "Y" };

		private static IClassFactory s_macroFunctionFactory = null;
		/// <summary>This maps from the messageID to the message format.
		/// The messageID is a unique string for each message in the system.
		/// The message format can contain "{0}", etc., which are replaced
		/// by actual parameters when the message is being built.
		/// </summary>
		protected INameAddressSupport m_activeCultureMaps = new FlexiMap();
		protected IBizObjList m_sitePages = null;

		protected IApplication m_app;
		protected PatternRepository m_patterns;
		protected IDictionary m_simpleMacros = new Hashtable();

		protected bool m_doneWithStartup = false;

		protected static IDictionary s_badHttpChars;
		protected static IDictionary s_badXmlChars;
		protected static IDictionary s_reverseBadXmlChars;
		static ResourceManager() 
		{
			s_badHttpChars = new Hashtable();
			s_badHttpChars["%"] = "";
			s_badHttpChars[" "] = "%20";
			s_badHttpChars["&"] = "%26";

			s_badXmlChars = new Hashtable();
			s_badXmlChars["&"] = "&amp;";
			s_badXmlChars["<"] = "&lt;";
			s_badXmlChars[">"] = "&gt;";

			s_reverseBadXmlChars = new Hashtable();
			s_reverseBadXmlChars["&gt;"] = ">";
			s_reverseBadXmlChars["&lt;"] = "<";
			s_reverseBadXmlChars["&amp;"] = "&";
		}

		public static IClassFactory MacroFunctionFactory
		{
			get
			{
				if(s_macroFunctionFactory == null)
					s_macroFunctionFactory = Application.currentApp.factories["MacroFunctions"];
				return s_macroFunctionFactory;
			}
		}

		public ResourceManager(IApplication app)
		{
			m_app = app;
			m_patterns = new PatternRepository(app);
		}

		public virtual void registerForConfig(IConfigManager config)
		{
			s_macroFunctionFactory = null;
			WebRequestCreator.resetClassFactoryReferences();

			config.registerHandler("...<Macro>", new MacroDefinitionConfigHandler(this));
			config.registerHandler("...<WebRequestHandler>", new WebRequestConfigHandler());

			m_patterns.registerForConfig(config);
		}

		public void checkConfigStatus()
		{
			m_patterns.checkConfigStatus();
			IDictionary newSimpleMacros = m_app.factories[@"Dictionaries"].createFromNickName(@"macro_versioning") as IDictionary;
			if (newSimpleMacros != null && newSimpleMacros.GetType() != m_simpleMacros.GetType())
			{
				foreach (DictionaryEntry de in m_simpleMacros)
					newSimpleMacros[de.Key] = de.Value;
				m_simpleMacros = newSimpleMacros;
			}

			pullMacroFunctions();
		}

		protected void pullMacroFunctions()
		{
			foreach (DotNetClassInfo info in MacroFunctionFactory.entries.Values)
				if(!m_simpleMacros.Contains(info.nickName))
				{
					IMacro m = (IMacro)info.create(info.nickName);
					m_simpleMacros[info.nickName] = m;
				}
		}

		public void clearTranslationCache(Swaf.BGuid.Guid translationId)
		{
			m_activeCultureMaps.put(translationId.ToString(), null);
		}

		public virtual string getMessage(object messageId, string defVal, params object[] replacements)
		{
			string msg = null;
			if (!UsingBuiltInCulture)
			{
				Swaf.BGuid.Guid translationId = CurrentCulture.get("translationId") as Swaf.BGuid.Guid;
				IBizObjList translations = m_activeCultureMaps.get(translationId.ToString()) as IBizObjList;
				if (translations == null)
					m_activeCultureMaps.put(translationId.ToString(), translations = m_app.ruleMgr.execute("getTranslationWithTexts", translationId).CallResults as IBizObjList);

				if (translations != null)
				{
					IBizObj textObj = translations.search("textId", messageId, false);
					if (textObj != null)
						msg = textObj.get("text").ToString();
				}
			}
			if (msg == null)
				msg = defVal;
			if(msg != null)
				msg = (replacements==null || replacements.Length == 0)?msg:string.Format(msg, replacements);

			return msg;
		}
		

		public virtual string resolve(string str)
		{
			if(str == null) return null;
			//return Cmn.replaceAll(simpleMacros, str);
			return doResolve(m_simpleMacros, str, null,false);
		}

		public virtual string resolve(string str, IDictionary localMacros)
		{
			if(str == null) return null;
			//return Cmn.replace(new DictionaryStack(localMacros, simpleMacros), str);
			//return Cmn.replace(simpleMacros, str);
			return doResolve(new DictionaryStack(localMacros, m_simpleMacros), str, null, false);
		}

		public virtual string resolve(string str, IDictionary macroExceptions, bool onlyExceptions)
		{
			if(str == null) return null;
			return doResolve(m_simpleMacros, str, macroExceptions, onlyExceptions);
		}

		public virtual string getResourceUrl(string namespaceName, string fileName)
		{
			return "";
		}

		public virtual string getMacro(string name)
		{
            IMacro macro = m_simpleMacros[name] as IMacro;
            return macro != null ? macro.calcValue(name, "", m_simpleMacros) : null;
		}

		public void defineMacro(string name, string val)
		{
			m_simpleMacros[name] = new SimpleMacro(val);
		}

		public virtual string getFileResourceUrl(string name, string fileName)
		{
			return "";
		}

		public virtual string generateRawPatternXml()
		{
			return m_patterns.generateRawPatternXml();
		}

		public virtual Regex knownGoodPattern(string pattern)
		{
			return m_patterns.knownGoodPattern(pattern);
		}
		
		public virtual string patternErrorMessage(string pattern)
		{
			return m_patterns.patternErrorMessage(pattern);
		}

		public virtual Match matches(string pattern, string target)
		{
			return matches(m_patterns.getPattern(pattern).regexpObject(), target);
		}
		
		public virtual Match matches(Regex pattern, string target)
		{
			return pattern.Match(target);
		}
		
		public virtual Match contains(string pattern, string target)
		{
			return contains(m_patterns.getPattern(pattern).regexpObject(), target);
		}
		
		public virtual Match contains(Regex pattern, string target)
		{
			return pattern.Match(target);
		}
		
		public virtual Match matchesPrefix(string pattern, string target)
		{
			if(pattern != null && pattern[0] != '^')
				pattern = "^" + pattern;
			return matchesPrefix(m_patterns.getPattern(pattern).regexpObject(), target);
		}
				
		public virtual Match matchesPrefix(Regex pattern, string target)
		{
			return pattern.Match(target);
		}
		
		public virtual string replace(string pattern, string target, string literal)
		{
			return null;
		}
		
		public virtual string replace(Regex pattern, string target, string literal)
		{
			return null;
		}
								
		public virtual IList split(string pattern, string target)
		{
			return split(m_patterns.getPattern(pattern).regexpObject(), target);
		}
				
		public virtual IList split(Regex pattern, string target)
		{
			return null;
		}		
	
		public virtual string fixupForHttp(string txt) 
		{
			return Cmn.replace(s_badHttpChars, txt);
		}

		public virtual string fixupForXml(string txt) 
		{
			return Cmn.replace(s_badXmlChars, txt);
		}

		public virtual string fixupFromXml(string txt)
		{
			return Cmn.replace(s_reverseBadXmlChars, txt);
		}

		protected string doResolve(IDictionary workset, string rawData, IDictionary macroExceptions, bool onlyExceptions)
		{
			bool m_pulledMacroFunctions = false;
			IList replacements = new ArrayList(50);
			string orgStr = rawData;
			int sizeDif = 0;
			
			//Step 1: scan rawData, looking for match occurences
			int rawLength = rawData.Length;
			for(int p = 0; p < rawLength; ++p)
			{
				if(rawData[p] == '$') //Found what could be the start of a macro.
				{
					int startParams = -1;
					int endPos = -1;
					int lookEndParam = 0;
					for(int p2 = p+1; p2 < rawLength; ++p2)
					{
						if(rawData[p2] == '(')
						{
							if(lookEndParam == 0)
								startParams = p2;
							lookEndParam++;
						}
						else if(rawData[p2] == '$' && lookEndParam == 0)
						{
							endPos = p2;
							break;
						}
						else if(lookEndParam > 0 && rawData[p2] == ')')
							lookEndParam--;
					}

					if(endPos != -1)
					{
						string macroName = null;
						string macroParams = null;
						if(startParams == -1)
							macroName  = rawData.Substring(p+1,endPos-p-1);
						else
						{
							macroName = rawData.Substring(p+1, startParams-p-1);
							macroParams = rawData.Substring(startParams+1, endPos-startParams-2);
						}
						if(!Cmn.IsEmpty(macroName) && (macroExceptions == null || 
							(onlyExceptions && macroExceptions.Contains(macroName)) ||
							(!onlyExceptions && !macroExceptions.Contains(macroName))))
						{
							object mObj = workset[macroName];
							IMacro m = null;
							if(mObj is IMacro)
								m = (IMacro)mObj;
							else if (mObj != null)
								m = new SimpleMacro(mObj.ToString());

							if(m != null)
							{
								string replacementText = resolve(macroParams,workset);
								replacementText = m.calcValue(macroName, replacementText, workset);
								if (replacementText != null && ((replacementText.Length != macroName.Length +2) || replacementText.Substring(1,replacementText.Length-2).CompareTo(macroName) != 0))
								{
									ReplaceOccurance ro = new ReplaceOccurance(p, endPos - p + 1, replacementText.Length, replacementText);
									replacements.Add(ro);
									sizeDif += ro.replaceLen - ro.paramLen;
								}
							}
							else 
								endPos =p;
						}
						else
							endPos = p;
						p = endPos;
					}
				}
			}

			//Step 2:Iterate through replacements and build string.
			if(replacements.Count > 0)
			{
				StringBuilder buff = new StringBuilder(rawData.Length + sizeDif);
				int lastPos = 0;
				foreach(ReplaceOccurance ro in replacements)
				{
					//Move static content between macros in rawData into the buffer
					for(int p = lastPos; p < ro.position; ++p)
						buff.Append(rawData[p]);

					//Move the current replacment into the buff.
					buff.Append(ro.replaceText);

					//Advance lastPos
					lastPos = ro.position + ro.paramLen;
				}
				for(int p = lastPos; p < rawLength; ++p)
					buff.Append(rawData[p]);
				return doResolve(workset, buff.ToString(), macroExceptions, onlyExceptions);
			}
			if (orgStr.Equals(rawData) && orgStr.StartsWith("$_"))
			{
				//This could be an environment variable request.  Try to get the value.
				int lastPos=rawData.IndexOf("$",2) - 2;
				if (lastPos > 2)
				{
					string envName = rawData.Substring(2,lastPos);
					string envVal=Environment.GetEnvironmentVariable(envName.ToLower());
					if (! envVal.Equals(string.Empty))
					{
						rawData=envVal;
					}
				}
			}
			return rawData;			
		}

		public virtual string format(DateTime source, DateTimeFormats format)
		{
			return source.ToString(s_shortDateTimeFormats[(int)format], UnderlyingCulture.DateTimeFormat);
		}
		public virtual string format(short source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(int source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(long source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(double source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(float source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(decimal source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(uint source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(ulong source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual string format(ushort source, NumericFormats format)
		{
			return source.ToString(s_shortNumericFormats[(int)format], UnderlyingCulture.NumberFormat);
		}
		public virtual DateTime dateFromString(string source)
		{
			return DateTime.Parse(source, UnderlyingCulture.DateTimeFormat);
		}

		public virtual object numberFromString(string source, NumberTypes type)
		{
			switch (type)
			{
				case NumberTypes.Decimal:
					return decimal.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.Double:
					return double.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.Float:
					return float.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.Int:
					return int.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.Long:
					return long.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.Short:
					return short.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.UInt:
					return uint.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.ULong:
					return ulong.Parse(source, UnderlyingCulture.NumberFormat);
				case NumberTypes.UShort:
					return ushort.Parse(source, UnderlyingCulture.NumberFormat);
			}
			return null;
		}

		public virtual IBizObj CurrentCulture
		{
			get
			{
				Culture culture = Application.currentApp.sessionData["CurrentCulture"] as Culture;
				return (culture == null)?BuiltInCulture:culture.Details;
			}
			set
			{
				if (value == null || (value.isKindOf("Culture") && value.get("translationId").Equals(BuiltInCulture.get("translationId"))))
					Application.currentApp.sessionData["CurrentCulture"] = null;
				else if (value.isKindOf("Culture"))
					Application.currentApp.sessionData["CurrentCulture"] = new Culture(value);
				else
					throw new GlobalizationException(string.Format("The bizobj type '{0}' is not a know type of Culture.", value.type.Name));
			}
		}

		public virtual IBizObj BuiltInCulture
		{
			get
			{
				IBizObj cultureDetails = null;
				Culture culture = Application.globalApp.appData["BuiltInCulture"] as Culture;
				if (culture == null && (cultureDetails = Application.globalApp.appData["BuiltInCulture"] as IBizObj) != null)
					Application.globalApp.appData["BuiltInCulture"] = culture = new Culture(cultureDetails);

				if (culture != null)
					cultureDetails = culture.Details;
				return cultureDetails;
			}
		}

		public bool UsingBuiltInCulture
		{
			get
			{
				Culture culture = Application.currentApp.sessionData["CurrentCulture"] as Culture;
				return culture == null;
			}
		}

		protected System.Globalization.CultureInfo UnderlyingCulture
		{
			get
			{
				Culture culture = Application.currentApp.sessionData["CurrentCulture"] as Culture;
				if (culture == null)
					culture = Application.globalApp.appData["DefaultCulture"] as Culture;
				return culture == null ? null : culture.BaseInfo;
			}
		}
		protected class Culture
		{
			public System.Globalization.CultureInfo BaseInfo;
			public IBizObj Details;
			public Culture(IBizObj details)
			{
				BaseInfo = new System.Globalization.CultureInfo((string)details.get("dotNetBaseCulture"));
				Details = details;
			}
		}
		
		private class ReplaceOccurance
		{
			public ReplaceOccurance(int position, int paramLen, int replaceLen, string replaceText)
			{
				this.position = position;
				this.paramLen = paramLen;
				this.replaceLen = replaceLen;
				this.replaceText = replaceText;
			}
			public int position;
			public int paramLen;
			public int replaceLen;
			public string replaceText;
		}

        private class WebRequestConfigHandler:ConfigHandler
		{
			protected static string[] s_legalAttributes = new string[]{"name"};
			public WebRequestConfigHandler()
			{
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
				configInfo.checkIllegalAttributes(s_legalAttributes);
				string name = configInfo.getAttribute("name");
				string type = configInfo.getChildText();
				if(name != null)
				{
					WebRequest.RegisterPrefix(name + ":", new WebRequestCreator(type));
					Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL,"Defined WebRequestHandler {0} with class type \"{1}\"",name,type);
				}
				else
				{
					//TODO Throw some kind of exception
				}

			}
		}

		private class MacroDefinitionConfigHandler:ConfigHandler
		{
			protected static string[] s_legalAttributes = new string[]{"name"};
			public MacroDefinitionConfigHandler(ResourceManager resMgr)
			{
				m_resMgr = resMgr;
			}
			public override void  executeBefore(IConfigElement configInfo)
			{
				//configInfo.checkIllegalAttributes(s_legalAttributes);
				IList otherAttributes = configInfo.checkUnknownAttributes(s_legalAttributes);
				string name = configInfo.getAttribute("name");
				string val = configInfo.getChildText("");
				if(name != null)
				{
					m_resMgr.defineMacro(name, val);
					Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL,"Defined Macro {0} with value \"{1}\"",name,val);
				}
				else
				{
					//TODO Throw some kind of exception
				}

			}
			private ResourceManager m_resMgr;
		}

	}
}